﻿using MudEngine.Parsing.AST;
using MudEngine.Runtime;
using System;
using System.IO;
using System.Linq;

namespace MudEngine.Parsing
{
	/// <summary>
	/// 
	/// </summary>
	public class Compiler
	{
		/// <summary>
		/// Holds the first pass code visitor
		/// </summary>
		private FirstPassVisitor firstPassVisitor = new FirstPassVisitor();
		/// <summary>
		/// Holds the Code Generator visitor
		/// </summary>
		private ICodeGenVisitor codeGenVisitor = new ICodeGenVisitor();
        /// <summary>
        /// 
        /// </summary>
        public Interpreter Interpreter { get; set; }
        public ObjectManager ObjectManager { get; set; }
        /// <summary>
		/// Compiles the program in the given file
		/// </summary>
		/// <param name="filename"></param>
		/// <returns></returns>
		public Program Compile(string filename, FileInfo fiSourceFile)
		{
			FileStream fiStream = fiSourceFile.OpenRead();
			Parser parser = new Parser(new Scanner(fiStream));
            parser.ObjectManager = ObjectManager;

			CompilationUnit rootUnit = parser.Parse();
			fiStream.Close();

			if (parser.errors.count == 0)
			{
                CodeGenContext context = new CodeGenContext(fiSourceFile);
                foreach (Function f in Interpreter.RuntimeFunctions)
                    context.AddIdentifier(f.Name, f);

				rootUnit.AcceptVisitor(firstPassVisitor, context);
				rootUnit.AcceptVisitor(codeGenVisitor, context);

                if (context.Errors.Count == 0)
                {
                    Program program = new Program();
                    program.Binary = context.GetProgramCode();
                    program.StringTable = context.Strings();
                    program.Inherits = context.Inherits();
                    program.Variables = context.Variables();
                    program.FunctionTable = context.Functions();
                    program.ProgramSize = program.Binary.Length;
                    program.Filename = filename;
                    program.LastChanged = fiSourceFile.LastWriteTime;
                    program.NumberOfVariables = context.Variables().Count;
                    program.TotalNumberOfVariables = context.TotalNumberOfVariables;

                    return program;
                }
			}

			return null;
		}
	}
	/// <summary>
	/// 
	/// </summary>
	public class FirstPassVisitor : AbstractAstNodeVisitor<CodeGenContext>
	{
		/// <summary>
		/// 
		/// </summary>
		/// <param name="compilationUnit"></param>
		/// <param name="ctx"></param>
		public override void VisitCompilationUnit(CompilationUnit compilationUnit, CodeGenContext ctx)
		{
			compilationUnit.Children.ForEach(delegate(AstNode node) { node.AcceptVisitor(this, ctx); });
		}
        /// <summary>
        /// 
        /// </summary>
        /// <param name="inheritDeclaration"></param>
        /// <param name="ctx"></param>
        public override void VisitInheritDeclaration(InheritDeclaration inheritDeclaration, CodeGenContext ctx)
        {
            ctx.DefineInherit(inheritDeclaration.Inherit, inheritDeclaration.Program);
        }
        /// <summary>
		/// First pass of a function definition
		/// </summary>
		/// <param name="fd"></param>
		/// <param name="ctx"></param>
		public override void VisitFunctionDeclaration(FunctionDeclaration fd, CodeGenContext ctx)
		{
            Function fun = ctx.DefineNewFunction(fd.Name, fd.Parameters.Count, fd.LocalDataSize, fd.Type, FunctionFlags.Prototype);

			for(int idx = 0; idx < fd.Parameters.Count; idx++)
			{
				ParameterDeclaration par = fd.Parameters[idx] as ParameterDeclaration;
                ctx.AddLocalVariable(fun, par.Name, par.Type, idx, Scope.Parameter);
			}
		}
		/// <summary>
		/// 
		/// </summary>
		/// <param name="fd"></param>
		/// <param name="ctx"></param>
		public override void VisitFieldDeclaration(FieldDeclaration fd, CodeGenContext ctx)
		{
			ctx.DefineNewVariable(fd.Name, fd.Type);
		}
		/// <summary>
		/// 
		/// </summary>
		/// <param name="enumDeclaration"></param>
		/// <param name="ctx"></param>
		public override void VisitEnumDeclaration(EnumDeclaration enumDeclaration, CodeGenContext ctx)
		{
			/*
			ctx.AddSymbol(Kind.Enumeration, enumDeclaration.Modifiers, enumDeclaration.Type, enumDeclaration.Name);

			foreach (EnumMemberDeclaration enumMember in enumDeclaration.Members)
				enumMember.AcceptVisitor(this, ctx);
			 */
		}
	}
	/// <summary>
	/// 
	/// </summary>
    public class ICodeGenVisitor : AbstractAstNodeVisitor<CodeGenContext>
    {
		/// <summary>
		/// Emits the code for all fields and functions
		/// </summary>
		/// <param name="compilationUnit"></param>
		/// <param name="ctx"></param>
		public override void VisitCompilationUnit(CompilationUnit compilationUnit, CodeGenContext ctx)
		{
			// Define a GLOBAL INIT function to initialize the global variables and inherited objects
			Function initFun = ctx.DefineNewFunction(Function.INIT_FUNC, 0, 0, AType.TYPE_VOID, FunctionFlags.Final);
			initFun.Flags |= FunctionFlags.Final;
			ctx.UpdateFunctionAddress(initFun);
			ctx.CurrentFunction = initFun;

            // handle inherits
            foreach (InheritDeclaration inheritDeclaration in compilationUnit.Children.OfType<InheritDeclaration>())
                inheritDeclaration.AcceptVisitor(this, ctx);

            // handle fields / enum first
			foreach (FieldDeclaration fieldDeclaration in compilationUnit.Children.OfType<FieldDeclaration>())
				fieldDeclaration.AcceptVisitor(this, ctx);

            // Return from the GLOBAL INIT function
			ctx.EmitOpCode(OpCodes.ldc_i1_0);
			ctx.EmitOpCode(OpCodes.ret);

			// handle functions
			foreach (FunctionDeclaration functionDeclaration in compilationUnit.Children.OfType<FunctionDeclaration>())
				functionDeclaration.AcceptVisitor(this, ctx);
		}
        /// <summary>
        /// 
        /// </summary>
        /// <param name="inheritDeclaration"></param>
        /// <param name="ctx"></param>
        public override void VisitInheritDeclaration(InheritDeclaration inheritDeclaration, CodeGenContext ctx)
        {
            // generate function calls to ___INIT function in inherited object
            Inherit inherit = ctx.FindInherit(inheritDeclaration.Inherit);
            if (inherit == null)
                ctx.AddErrorFormat("'{0}' is not inherited", inheritDeclaration.Inherit);
            else if (inherit.Program != null)
            {
                // Emit index of inherited object
                ctx.EmitOpCodeB(OpCodes.call_i, (byte)inherit.Index);
                // emit index of init function
                ctx.Emit2((short)(inherit.Program.FunctionTable.Count - 1));
                ctx.Emit1((byte)0); // emit 0 arguments
                ctx.EmitOpCode(OpCodes.pop);
            }
        }
		/// <summary>
		/// Emits the code for the global variables with initializers (if applicable)
		/// </summary>
		/// <param name="fd"></param>
		/// <param name="ctx"></param>
		public override void VisitFieldDeclaration(FieldDeclaration fd, CodeGenContext ctx)
		{
			Identifier ident = ctx.FindIdentifier(fd.Name);
            if (ident == null)
				ctx.AddErrorFormat("Undefined variable '{0}'", fd.Name);
			else
			{
				if (fd.Initializer != null)
				{
					fd.Initializer.AcceptVisitor(this, ctx);
                    ctx.EmitOpCode(OpCodes.sglob, ident.Address);
				}

				CheckExpressionResult result = fd.CheckInitializer();
				if (result.HasFlag(CheckExpressionResult.RIGHT))
					ctx.AddErrorFormat("Bad initializer type for field '{0}'", fd.Name);
			}
		}
		/// <summary>
		/// Emits the code for the Functions
		/// </summary>
		/// <param name="fd"></param>
		/// <param name="ctx"></param>
		public override void VisitFunctionDeclaration(FunctionDeclaration fd, CodeGenContext ctx)
		{
            Function function = ctx.DefineNewFunction(fd.Name, fd.Parameters.Count, fd.LocalDataSize, fd.Type, FunctionFlags.Final);

			ctx.UpdateFunctionAddress(function);

			ctx.CurrentFunction = function;

			BlockStatement blockStatement = fd.Body;
			Statement lastStatement = null;
			foreach (Statement stat in blockStatement.Children)
			{
				stat.AcceptVisitor(this, ctx);
				lastStatement = stat;
			}

			if (!(lastStatement is ReturnStatement && ((ReturnStatement)lastStatement).Expression != null))
				ctx.EmitOpCode(OpCodes.ldc_i1_0);
            function.EndAddress = ctx.PC;
			ctx.EmitOpCode(OpCodes.ret);
		}
		/// <summary>
		/// Emits the code in a block statement
		/// </summary>
		/// <param name="blockStatement"></param>
		/// <param name="ctx"></param>
		public override void VisitBlockStatement(BlockStatement blockStatement, CodeGenContext ctx)
		{
			blockStatement.Children.ForEach(delegate(AstNode stat) { stat.AcceptVisitor(this, ctx); });
		}
		/// <summary>
		/// Emits the code for the variable declaration
		/// </summary>
		/// <param name="variableDeclarationStatement"></param>
		/// <param name="ctx"></param>
		public override void VisitVariableDeclarationStatement(VariableDeclarationStatement variableDeclarationStatement, CodeGenContext ctx)
		{
			Variable var = ctx.CurrentFunction.FindVariable(variableDeclarationStatement.Name);
			if (var != null)
				ctx.AddWarningFormat("Redeclaration of local variable {0}", variableDeclarationStatement.Name);

            var = ctx.AddLocalVariable(ctx.CurrentFunction, variableDeclarationStatement.Name, variableDeclarationStatement.Type, -1, Scope.Local);
			if (variableDeclarationStatement.Initializer != null)
			{
				variableDeclarationStatement.Initializer.AcceptVisitor(this, ctx);
				ctx.EmitOpCode(OpCodes.sloc, var.Address);

				CheckExpressionResult result = variableDeclarationStatement.CheckInitializer();
				if (result.HasFlag(CheckExpressionResult.RIGHT))
					ctx.AddErrorFormat("Bad initializer type for variable '{0}'", variableDeclarationStatement.Name);
			}
		}
		/// <summary>
		/// Emits code for constants
		/// </summary>
		/// <param name="constantExpression"></param>
		/// <param name="ctx"></param>
		public override void VisitConstantExpression(ConstantExpression constantExpression, CodeGenContext ctx)
		{
			if (constantExpression.Type == AType.TYPE_BOOL)
			{
				if (constantExpression.Bool)
					ctx.EmitOpCode(OpCodes.ldc_i1_1);
				else
					ctx.EmitOpCode(OpCodes.ldc_i1_0);
			}
			else if (constantExpression.Type == AType.TYPE_SHORT)
			{
				if (constantExpression.Short == 0)
					ctx.EmitOpCode(OpCodes.ldc_i2_0);
				else if (constantExpression.Short == 1)
					ctx.EmitOpCode(OpCodes.ldc_i2_1);
				else
				{
					ctx.EmitOpCode(OpCodes.ldc_i2);
					ctx.Emit2(constantExpression.Short);
				}
			}
			else if (constantExpression.Type == AType.TYPE_INT)
			{
				if (constantExpression.Int == 0)
					ctx.EmitOpCode(OpCodes.ldc_i4_0);
				else if (constantExpression.Int == 1)
					ctx.EmitOpCode(OpCodes.ldc_i4_1);
				else
				{
					ctx.EmitOpCode(OpCodes.ldc_i4);
					ctx.Emit4(constantExpression.Int);
				}
			}
			else if (constantExpression.Type == AType.TYPE_LONG)
			{
				ctx.EmitOpCode(OpCodes.ldc_i8);
				ctx.Emit8(constantExpression.Long);
			}
			else if (constantExpression.Type == AType.TYPE_STRING)
			{
				// Store string in string table
				int strIdx = ctx.AddString(constantExpression.String);
				// push reference to string entry on stack
				ctx.EmitOpCode(OpCodes.lstr, strIdx);
			}
		}
		/// <summary>
		/// Emits code for binary expressions
		/// </summary>
		/// <param name="binaryExpression"></param>
		/// <param name="ctx"></param>
		public override void VisitBinaryExpression(BinaryExpression binaryExpression, CodeGenContext ctx)
		{
			binaryExpression.Left.AcceptVisitor(this, ctx);
			binaryExpression.Right.AcceptVisitor(this, ctx);

			CheckExpressionResult result = binaryExpression.CheckExpressions();
			if (result.HasFlag(CheckExpressionResult.LEFT) && result.HasFlag(CheckExpressionResult.RIGHT))
				ctx.AddErrorFormat("Arguments to '{0}' do not have compatible types.", BinaryExpression.OperatorName(binaryExpression.Operator));
			else if (result.HasFlag(CheckExpressionResult.LEFT))
				ctx.AddErrorFormat("Bad left argument to '{0}'", BinaryExpression.OperatorName(binaryExpression.Operator));
			else if (result.HasFlag(CheckExpressionResult.RIGHT))
				ctx.AddErrorFormat("Bad right argument to '{0}'", BinaryExpression.OperatorName(binaryExpression.Operator));
			else
			{
				// check types of left and right side to determine what the overall type will become.
				// ie: string + number ==> string
				//     number + string ==> string
				//     number + number ==> number
				//     real + number ==> real
				AType resultType = AType.TYPE_UNKNOWN;
				AType t1 = binaryExpression.Left.Type;
				AType t2 = binaryExpression.Right.Type;

//				if (t1 == t2)
//					resultType = t1;
//				else
//				{
					switch (binaryExpression.Operator)
					{
						case BinaryOperatorType.Add:
							if (t1 == AType.TYPE_SHORT || t1 == AType.TYPE_INT || t1 == AType.TYPE_LONG)
							{
								if (t2 == AType.TYPE_REAL)
									resultType = AType.TYPE_REAL;
								else if (t2 == AType.TYPE_STRING)
									resultType = AType.TYPE_STRING;
							}
							else if (t1 == AType.TYPE_REAL)
							{
								if (t2 == AType.TYPE_STRING)
									resultType = AType.TYPE_STRING;
								else
									resultType = AType.TYPE_REAL;
							}
							else if (t1 == AType.TYPE_STRING)
							{
								resultType = AType.TYPE_STRING;
							}
							else if (t1 == AType.TYPE_ARRAY)
							{
                                resultType = AType.TYPE_ARRAY;
							}
							else if (t1 == AType.TYPE_MAP)
							{
                                resultType = AType.TYPE_MAP;
							}
							ctx.EmitOpCode(OpCodes.add);
							break;
						case BinaryOperatorType.Modulus:
							if (t1 == AType.TYPE_REAL || t2 == AType.TYPE_REAL)
								resultType = AType.TYPE_REAL;
							else
								resultType = t1;
							ctx.EmitOpCode(OpCodes.mod); 
							break;
						case BinaryOperatorType.Multiply: 
							if (t1 == AType.TYPE_REAL || t2 == AType.TYPE_REAL)
								resultType = AType.TYPE_REAL;
							else
								resultType = t1;
							ctx.EmitOpCode(OpCodes.mul); 
							break;
						case BinaryOperatorType.ShiftLeft:
							resultType = t1;
							ctx.EmitOpCode(OpCodes.shl); 
							break;
						case BinaryOperatorType.ShiftRight:
							resultType = t1;
							ctx.EmitOpCode(OpCodes.shr); 
							break;
						case BinaryOperatorType.Subtract: 
							if (t1 == AType.TYPE_REAL || t2 == AType.TYPE_REAL)
								resultType = AType.TYPE_REAL;
							else
								resultType = t1;
							ctx.EmitOpCode(OpCodes.sub); 
							break;
						case BinaryOperatorType.BitwiseAnd:
							resultType = t1;
							ctx.EmitOpCode(OpCodes.and);
							break;
						case BinaryOperatorType.BitwiseOr:
							resultType = t1;
							ctx.EmitOpCode(OpCodes.or); 
							break;
						case BinaryOperatorType.Divide: 
							if (t1 == AType.TYPE_REAL || t2 == AType.TYPE_REAL)
								resultType = AType.TYPE_REAL;
							else
								resultType = t1;
							ctx.EmitOpCode(OpCodes.div); 
							break;
						case BinaryOperatorType.ConditionalAnd:
							resultType = AType.TYPE_BOOL;
							ctx.EmitOpCode(OpCodes.cand); 
							break;
						case BinaryOperatorType.ConditionalOr:
							resultType = AType.TYPE_BOOL;
							ctx.EmitOpCode(OpCodes.cor); 
							break;
						case BinaryOperatorType.Equality:
							resultType = AType.TYPE_BOOL;
							ctx.EmitOpCode(OpCodes.ceq); 
							break;
						case BinaryOperatorType.ExclusiveOr:
							resultType = AType.TYPE_BOOL;
							ctx.EmitOpCode(OpCodes.xor); 
							break;
						case BinaryOperatorType.GreaterThan:
							resultType = AType.TYPE_BOOL;
							ctx.EmitOpCode(OpCodes.gt); 
							break;
						case BinaryOperatorType.GreaterThanOrEqual:
							resultType = AType.TYPE_BOOL;
							ctx.EmitOpCode(OpCodes.gte); 
							break;
						case BinaryOperatorType.InEquality: 
							resultType = AType.TYPE_BOOL;
							ctx.EmitOpCode(OpCodes.cneq); 
							break;
						case BinaryOperatorType.LessThan:
							resultType = AType.TYPE_BOOL;
							ctx.EmitOpCode(OpCodes.lt); 
							break;
						case BinaryOperatorType.LessThanOrEqual:
							resultType = AType.TYPE_BOOL;
							ctx.EmitOpCode(OpCodes.lte); 
							break;
					}
//				}
				binaryExpression.Type = resultType;
			}
		}
		/// <summary>
		/// Emits the code for loading an identifier
		/// </summary>
		/// <param name="identifierExpression"></param>
		/// <param name="ctx"></param>
		public override void VisitIdentifierExpression(IdentifierExpression identifierExpression, CodeGenContext ctx)
		{
			Identifier ident = ctx.FindIdentifier(identifierExpression.Name);
            if (ident == null)
				ctx.AddErrorFormat("Undefined identifier '{0}'", identifierExpression.Name);
			else
			{
                ctx.CurrentVariable = ident as Variable;
                if (ident is Variable)
                    identifierExpression.Type = ident.Type;
                else
                    identifierExpression.Type = AType.TYPE_FUNCTION;

                if (ident.Scope == Scope.Global)
                    ctx.EmitOpCode(OpCodes.lglob, ident.Address);
                else if (ident.Scope == Scope.Parameter)
                    ctx.EmitOpCode(OpCodes.larg, ident.Address);
                else
                    ctx.EmitOpCode(OpCodes.lloc, ident.Address);
			}
		}
		/// <summary>
		/// Emits code for an ExpressionStatement
		/// </summary>
		/// <param name="expressionStatement"></param>
		/// <param name="ctx"></param>
		public override void VisitExpressionStatement(ExpressionStatement expressionStatement, CodeGenContext ctx)
		{
			expressionStatement.Expression.AcceptVisitor(this, ctx);
			expressionStatement.Type = expressionStatement.Expression.Type;
		}
        /// <summary>
        /// 
        /// </summary>
        /// <param name="lambdaExpression"></param>
        /// <param name="ctx"></param>
        public override void VisitLambdaExpression(LambdaExpression lambdaExpression, CodeGenContext ctx)
        {
            throw new NotImplementedException("LambdaExpressions are not yet implemented");
        }
		/// <summary>
		/// Emits code for an assignment expression
		/// </summary>
		/// <param name="assignmentExpression"></param>
		/// <param name="ctx"></param>
		public override void VisitAssignmentExpression(AssignmentExpression assignmentExpression, CodeGenContext ctx)
		{
			if (assignmentExpression.Left is IdentifierExpression)
			{
				IdentifierExpression ie = assignmentExpression.Left as IdentifierExpression;
				Identifier ident = ctx.FindIdentifier(ie.Name);
                if (ident == null)
					ctx.AddErrorFormat("Undefined identifier '{0}'", ie.Name);
				else
				{
                    ie.Type = ident.Type;

                    assignmentExpression.Right.AcceptVisitor(this, ctx);

                    CheckExpressionResult result = assignmentExpression.CheckExpressions();
					if (result.HasFlag(CheckExpressionResult.LEFT))
						ctx.AddErrorFormat("Bad left argument to '{0}'", AssignmentExpression.OperatorName(assignmentExpression.Operator));
					else if (result.HasFlag(CheckExpressionResult.RIGHT))
						ctx.AddErrorFormat("Bad right argument to '{0}'", AssignmentExpression.OperatorName(assignmentExpression.Operator));

                    if (ident.Scope == Scope.Global)
                        ctx.EmitOpCode(OpCodes.sglob, ident.Address);
                    else if (ident.Scope == Scope.Parameter)
                        ctx.EmitOpCode(OpCodes.sarg, ident.Address);
					else
                        ctx.EmitOpCode(OpCodes.sloc, ident.Address);
				}
				assignmentExpression.Type = ie.Type;
            }
            else if (assignmentExpression.Left is IndexedIdentifierExpression)
            {
                IndexedIdentifierExpression iie = assignmentExpression.Left as IndexedIdentifierExpression;
                iie.Identifier.AcceptVisitor(this, ctx);
                iie.Index.AcceptVisitor(this, ctx);
                assignmentExpression.Right.AcceptVisitor(this, ctx);

                ctx.EmitOpCode(OpCodes.stelem);

                assignmentExpression.Type = AType.TYPE_VAR;
            }
            else
            {
                ctx.AddWarningFormat("Unknown LHS {0} in Assignment expression.", assignmentExpression.Left.GetType());
            }
		}
		/// <summary>
		/// Emits code the invocation of a function
		/// </summary>
		/// <param name="invocationStatement"></param>
		/// <param name="ctx"></param>
		public override void VisitInvocationExpression(InvocationExpression invocationStatement, CodeGenContext ctx)
		{
            int numArguments = invocationStatement.Arguments.Count;
            Function function = null;
            string functionName = string.Empty;
            Inherit inh = null;
            if (invocationStatement.CallType == InvocationType.CALL_OTHER)
            {
                // push other object on the stack
                invocationStatement.Target.AcceptVisitor(this, ctx);
                // Store string in string table
                int strIdx = ctx.AddString(invocationStatement.FunctionName);
                // push function name on the stack
                ctx.EmitOpCode(OpCodes.lstr, strIdx);
                // There are two extra arguments on the stack; The object and function name
                numArguments += 2;
                // Load call_other function
                function = ctx.FindFunction("call_other");
                functionName = invocationStatement.FunctionName;
            }
            else if (invocationStatement.CallType == InvocationType.CALL_INHERITED)
             {
                // handle calls to inherited functions "inherited::function()"
                IdentifierExpression e = invocationStatement.Target as IdentifierExpression;
                if (e == null)
                {
                    ctx.AddErrorFormat("Illegal call to inherited method");
                    return;
                }

                foreach (Inherit inherit in ctx.Inherits())
                {
                    if (inherit.Program.Filename.EndsWith(string.Format("/{0}.c", e.Name)))
                    {
                        inh = inherit;
                        function = inherit.Program.FindFunction(invocationStatement.FunctionName);
                    }
                }

                functionName = string.Format("{0}::{1}", e.Name, invocationStatement.FunctionName);
            }
            else
            {
                IdentifierExpression e = invocationStatement.Target as IdentifierExpression;
                if (e == null)
                {
                    ctx.AddErrorFormat("Illegal call to method {0}", invocationStatement.Target.GetType());
                    return;
                }
                function = ctx.FindFunction(e.Name);
                functionName = e.Name;
            }

			foreach (Expression argExpression in invocationStatement.Arguments)
				argExpression.AcceptVisitor(this, ctx);

            int idx = -1;
			if (function == null)
			{
                ctx.AddErrorFormat("Undefined function {0}", functionName);
				return;
			}
			else
				idx = function.Index;

			invocationStatement.Type = function.Type;
            if (invocationStatement.CallType == InvocationType.CALL_INHERITED)
            {
                // Emit index of inherited object
                ctx.EmitOpCodeB(OpCodes.call_i, (byte)inh.Index);
                // emit index of init function
                ctx.Emit2((short)idx);
            }
            else if (function.Flags.HasFlag(FunctionFlags.Runtime))
                ctx.EmitOpCodeS(OpCodes.call_virt, (short)idx);
            else
                ctx.EmitOpCodeS(OpCodes.call, (short)idx);
            ctx.Emit1((byte)numArguments);

			if (invocationStatement.Parent is ExpressionStatement)
				ctx.EmitOpCode(OpCodes.pop);
		}
		/// <summary>
		/// Emits the code for an unary expression
		/// </summary>
		/// <param name="unaryExpression"></param>
		/// <param name="ctx"></param>
		public override void VisitUnaryExpression(UnaryExpression unaryExpression, CodeGenContext ctx)
		{
			unaryExpression.Expression.AcceptVisitor(this, ctx);
			unaryExpression.Type = unaryExpression.Expression.Type;

			bool binaryExpression = unaryExpression.Parent is BinaryExpression;

			switch (unaryExpression.Operator)
			{
				case UnaryOperatorType.Increment:
					ctx.EmitOpCode(OpCodes.ldc_i1_1);
					ctx.EmitOpCode(OpCodes.add);
 					if (binaryExpression)
						ctx.EmitOpCode(OpCodes.dup);
					ctx.EmitOpCode(OpCodes.sloc, ctx.CurrentVariable.Address); 
					break;
				case UnaryOperatorType.Decrement:
					ctx.EmitOpCode(OpCodes.ldc_i1_1);
					ctx.EmitOpCode(OpCodes.sub); 
 					if (binaryExpression)
						ctx.EmitOpCode(OpCodes.dup);
					ctx.EmitOpCode(OpCodes.sloc, ctx.CurrentVariable.Address); 
					break;
				case UnaryOperatorType.PostIncrement: 
 					if (binaryExpression)
						ctx.EmitOpCode(OpCodes.dup);
					ctx.EmitOpCode(OpCodes.ldc_i1_1);
					ctx.EmitOpCode(OpCodes.add);
					ctx.EmitOpCode(OpCodes.sloc, ctx.CurrentVariable.Address); 
					break;
				case UnaryOperatorType.PostDecrement: 
 					if (binaryExpression)
						ctx.EmitOpCode(OpCodes.dup);
					ctx.EmitOpCode(OpCodes.ldc_i1_1);
					ctx.EmitOpCode(OpCodes.sub);
					ctx.EmitOpCode(OpCodes.sloc, ctx.CurrentVariable.Address); 
					break;
                case UnaryOperatorType.Not:
                    ctx.EmitOpCode(OpCodes.ldc_i1_0);
                    ctx.EmitOpCode(OpCodes.ceq);
                    break;
			}
		}
		/// <summary>
		/// Emits the code for the return statement
		/// </summary>
		/// <param name="returnStatement"></param>
		/// <param name="ctx"></param>
		public override void VisitReturnStatement(ReturnStatement returnStatement, CodeGenContext ctx)
		{
			if (returnStatement.Expression != null)
			{
				returnStatement.Expression.AcceptVisitor(this, ctx);
				returnStatement.Type = returnStatement.Expression.Type;
			}
			else
				returnStatement.Type = AType.TYPE_VOID;
		}
        /// <summary>
        /// 
        /// </summary>
        /// <param name="catchExpression"></param>
        /// <param name="ctx"></param>
        public override void VisitCatchExpression(CatchExpression catchExpression, CodeGenContext ctx)
        {
            int adr = ctx.EmitOpCode(OpCodes.catch_s, 0);
            if (catchExpression.Expression != null)
            {
                catchExpression.Expression.AcceptVisitor(this, ctx);
            }
            ctx.EmitOpCode(OpCodes.catch_e);
            ctx.PatchCode(adr, ctx.PC);
        }
		/// <summary>
		/// Emits code for a parenthesized expression. Just an expression wrapped in (<expression>)
		/// </summary>
		/// <param name="parenthesizedExpression"></param>
		/// <param name="ctx"></param>
		public override void VisitParenthesizedExpression(ParenthesizedExpression parenthesizedExpression, CodeGenContext ctx)
		{
			if (parenthesizedExpression.Expression == null)
			{
				ctx.AddErrorFormat("Illegal parenthesized expression defined");
				parenthesizedExpression.Type = AType.TYPE_VOID;
				return;
			}
			parenthesizedExpression.Expression.AcceptVisitor(this, ctx);
			parenthesizedExpression.Type = parenthesizedExpression.Expression.Type;
		}
		/// <summary>
		/// Emits the code for the if/else expression
		/// </summary>
		/// <param name="ifElseStatement"></param>
		/// <param name="ctx"></param>
		public override void VisitIfElseExpression(IfElseExpression ifElseStatement, CodeGenContext ctx)
		{
			int trueInstr = -1;
			if (ifElseStatement.Condition == null)
			{
				ctx.AddErrorFormat("Missing condition in if/else expression");
				return;
			}
			ifElseStatement.Condition.AcceptVisitor(this, ctx);
			// emit code for the jump to the false-statement of this expression
			int falseInstr = ctx.EmitOpCode(OpCodes.jmpf, 0);

			if (ifElseStatement.TrueStatement == null)
			{
				ctx.AddErrorFormat("Missing true statement in if/else expression");
				return;
			}
			ifElseStatement.TrueStatement.AcceptVisitor(this, ctx);
			// emit code for the jump to the end of true statement
			if (ifElseStatement.FalseStatement != null)
				trueInstr = ctx.EmitOpCode(OpCodes.jmp, 0);
			// patch the jump to the false statement with the current program counter value
			ctx.PatchCode(falseInstr, ctx.PC);
			// emits the code for the false statement
			if (ifElseStatement.FalseStatement != null)
			{
				ifElseStatement.FalseStatement.AcceptVisitor(this, ctx);
				// jump to end if else statement
				ctx.PatchCode(trueInstr, ctx.PC);

                //if (ifElseStatement.TrueStatement.Type != AType.TYPE_VAR && ifElseStatement.FalseStatement.Type != AType.TYPE_VAR && ifElseStatement.TrueStatement.Type != ifElseStatement.FalseStatement.Type)
                //    ctx.AddError("Both the true and false statement must result in same type.");
            }

            ifElseStatement.Type = ifElseStatement.TrueStatement.Type;
		}
		/// <summary>
		/// Emits the code for a while statement
		/// </summary>
		/// <param name="whileStatement"></param>
		/// <param name="ctx"></param>
		public override void VisitWhileStatement(WhileStatement whileStatement, CodeGenContext ctx)
		{
			// store the position before the condition check of the while statement
			int loopStart = ctx.PC;
			if (whileStatement.Condition == null)
			{
				ctx.AddErrorFormat("Missing condition statement in while statement");
				return;
			}
			whileStatement.Condition.AcceptVisitor(this, ctx);

			int adr = ctx.EmitOpCode(OpCodes.jmpf, 0);

			if (whileStatement.Statement == null)
			{
				ctx.AddErrorFormat("Missing inner statement in while statement");
				return;
			}
			whileStatement.Statement.AcceptVisitor(this, ctx);

			ctx.EmitOpCode(OpCodes.jmp, loopStart);
			ctx.PatchCode(adr, ctx.PC);
		}
		/// <summary>
		/// Emits the code for a cast expression
		/// </summary>
		/// <param name="castExpression"></param>
		/// <param name="ctx"></param>
		public override void VisitCastExpression(CastExpression castExpression, CodeGenContext ctx)
		{
			if (castExpression.Expression == null)
			{
				ctx.AddErrorFormat("Missing expression in cast expression");
				return;
			}
			castExpression.Expression.AcceptVisitor(this, ctx);

			CheckExpressionResult result = castExpression.CheckExpressions();
			if (result != CheckExpressionResult.OK)
				ctx.AddErrorFormat("Cannot cast {0} to {1}", castExpression.Expression.Type, castExpression.Type);
		}
        /// <summary>
        /// 
        /// </summary>
        /// <param name="forStatement"></param>
        /// <param name="ctx"></param>
        public override void VisitForStatement(ForStatement forStatement, CodeGenContext ctx)
        {
            foreach (Statement stat in forStatement.Initializers)
                stat.AcceptVisitor(this, ctx);

            int loopStart = ctx.PC;
            forStatement.Expression.AcceptVisitor(this, ctx);

            int adr = ctx.EmitOpCode(OpCodes.jmpf, 0);
            forStatement.Statement.AcceptVisitor(this, ctx);

            forStatement.Inc.AcceptVisitor(this, ctx);

            ctx.EmitOpCode(OpCodes.jmp, loopStart);
            ctx.PatchCode(adr, ctx.PC);

        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="mapExpression"></param>
        /// <param name="ctx"></param>
        public override void VisitMapExpression(MapInitializerExpression mapExpression, CodeGenContext ctx)
        {
            int count = mapExpression.Keys.Count;
            if (mapExpression.Keys.Count != mapExpression.Values.Count)
            {
                // I don't think this can happen, because the parser would have complaint already.
                ctx.AddWarningFormat("Illegal map initialization. There are {0} key(s) and {1} value(s). ", mapExpression.Keys.Count, mapExpression.Values.Count);
                count = Math.Min(count, mapExpression.Values.Count);
            }
            if (count > byte.MaxValue)
            {
                ctx.AddWarningFormat("Number of elements in the map initializer cannot exceed {0}.", byte.MaxValue);
                count = byte.MaxValue;
            }
            for (int idx = 0; idx < count; idx++)
            {
                mapExpression.Keys[idx].AcceptVisitor(this, ctx);
                mapExpression.Values[idx].AcceptVisitor(this, ctx);
            }
            ctx.EmitOpCodeB(OpCodes.acol, (byte)count);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="indexedIdentifierExpression"></param>
        /// <param name="ctx"></param>
        public override void VisitIndexedIdentifierExpression(IndexedIdentifierExpression indexedIdentifierExpression, CodeGenContext ctx)
        {
            indexedIdentifierExpression.Identifier.AcceptVisitor(this, ctx);
            indexedIdentifierExpression.Index.AcceptVisitor(this, ctx);

            if (indexedIdentifierExpression.RangeEnd != null)
            {
                indexedIdentifierExpression.RangeEnd.AcceptVisitor(this, ctx);

                if (indexedIdentifierExpression.IndexType == ArrayRangeType.Default)
                {
                    if (indexedIdentifierExpression.RangeEndType == ArrayRangeType.Default)
                        ctx.EmitOpCode(OpCodes.idx_nn);
                    else
                        ctx.EmitOpCode(OpCodes.idx_ne);
                }
                else
                {
                    if (indexedIdentifierExpression.RangeEndType == ArrayRangeType.Default)
                        ctx.EmitOpCode(OpCodes.idx_en);
                    else
                        ctx.EmitOpCode(OpCodes.idx_ee);
                }
            }
            else
            {
                if (indexedIdentifierExpression.IndexType == ArrayRangeType.Default)
                    ctx.EmitOpCode(OpCodes.idx_n);
                else
                    ctx.EmitOpCode(OpCodes.idx_e);
            }
            // There might be scenario's where the type is known I assume. Not sure yet if that is easy to figure out at compile time. It is easy to do during runtime.
            indexedIdentifierExpression.Type = AType.TYPE_VAR;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="arrayInitializerExpression"></param>
        /// <param name="ctx"></param>
        public override void VisitArrayInitializerExpression(ArrayInitializerExpression arrayInitializerExpression, CodeGenContext ctx)
        {
            int count = arrayInitializerExpression.Children.Count;
            foreach (Expression element in arrayInitializerExpression.Children)
                element.AcceptVisitor(this, ctx);
            if (count > byte.MaxValue)
            {
                ctx.AddWarningFormat("Number of elements in the array initializer cannot exceed {0}.", byte.MaxValue);
                count = byte.MaxValue;
            }
            ctx.EmitOpCodeB(OpCodes.col, (byte)count);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="forEachStatement"></param>
        /// <param name="ctx"></param>
        public override void VisitForEachStatement(ForEachStatement forEachStatement, CodeGenContext ctx)
        {
            Variable var = ctx.CurrentFunction.FindVariable(forEachStatement.Identifier);
            if (var != null)
                ctx.AddWarningFormat("Redeclaration of local variable {0}", forEachStatement.Identifier);
            var = ctx.AddLocalVariable(ctx.CurrentFunction, forEachStatement.Identifier, forEachStatement.ArrayType, -1, Scope.Local);

            Variable currentIdx = ctx.AddLocalVariable(ctx.CurrentFunction, string.Format("C${0}${1:00000}", var.Address + 1, ctx.PC), AType.TYPE_INT, -1, Scope.Local);
            Variable array = ctx.AddLocalVariable(ctx.CurrentFunction, string.Format("C${0}${1:00000}", currentIdx.Address + 1, ctx.PC), AType.TYPE_VAR, -1, Scope.Local);

            forEachStatement.Array.AcceptVisitor(this, ctx);

            ctx.EmitOpCode(OpCodes.sloc, array.Address);
            ctx.EmitOpCode(OpCodes.ldc_i4_0);
            ctx.EmitOpCode(OpCodes.sloc, currentIdx.Address);

            // first move to the end to get the length of the array and compare it to the current index (0)
            int nextAdr = ctx.EmitOpCode(OpCodes.jmp, 0);
            int loopStart = ctx.PC;

            // load array
            ctx.EmitOpCode(OpCodes.lloc, array.Address);
            // load next index in array
            ctx.EmitOpCode(OpCodes.lloc, currentIdx.Address);
            // push indexed element from array on stack
            ctx.EmitOpCode(OpCodes.idx_n);
            // store element in local variable
            ctx.EmitOpCode(OpCodes.sloc, var.Address);

            // Execute nested block of statement in this foreach statement
            forEachStatement.Statement.AcceptVisitor(this, ctx);

            // increment index counter
            ctx.EmitOpCode(OpCodes.lloc, currentIdx.Address);
            ctx.EmitOpCode(OpCodes.ldc_i4_1);
            ctx.EmitOpCode(OpCodes.add);
            ctx.EmitOpCode(OpCodes.sloc, currentIdx.Address);

            // mark first jump point
            ctx.PatchCode(nextAdr, ctx.PC);

            // compare index with array length
            ctx.EmitOpCode(OpCodes.lloc, currentIdx.Address);
            // call sizeof on array
            ctx.EmitOpCode(OpCodes.lloc, array.Address);
            Function sizeofFunction = ctx.FindFunction("sizeof");
            ctx.EmitOpCodeS(OpCodes.call_virt, (short)sizeofFunction.Index);
            ctx.Emit1((byte)sizeofFunction.NumArguments);
            // compare size of array with current index
            ctx.EmitOpCode(OpCodes.lt);
            // jump back to begin of loop if there are more elements in the array
            ctx.EmitOpCode(OpCodes.jmpt, loopStart);
        }
	}
}
